﻿using System.Windows.Forms;
using System.IO;
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;

namespace Kyoh.Lib.WinForms
{
    public class ShellOperations
    {

        #region ShellFolder Enums
        // Enums for standard Windows shell folders
        public enum ShellFolder
        {
            Desktop = Shell32.ShellSpecialFolderConstants.ssfDESKTOP,
            DesktopDirectory = Shell32.ShellSpecialFolderConstants.ssfDESKTOPDIRECTORY,
            MyComputer = Shell32.ShellSpecialFolderConstants.ssfDRIVES,
            MyDocuments = Shell32.ShellSpecialFolderConstants.ssfPERSONAL,
            MyPictures = Shell32.ShellSpecialFolderConstants.ssfMYPICTURES,
            History = Shell32.ShellSpecialFolderConstants.ssfHISTORY,
            Favorites = Shell32.ShellSpecialFolderConstants.ssfFAVORITES,
            Fonts = Shell32.ShellSpecialFolderConstants.ssfFONTS,
            ControlPanel = Shell32.ShellSpecialFolderConstants.ssfCONTROLS,
            TemporaryInternetFiles = Shell32.ShellSpecialFolderConstants.ssfINTERNETCACHE,
            MyNetworkPlaces = Shell32.ShellSpecialFolderConstants.ssfNETHOOD,
            NetworkNeighborhood = Shell32.ShellSpecialFolderConstants.ssfNETWORK,
            ProgramFiles = Shell32.ShellSpecialFolderConstants.ssfPROGRAMFILES,
            RecentFiles = Shell32.ShellSpecialFolderConstants.ssfRECENT,
            StartMenu = Shell32.ShellSpecialFolderConstants.ssfSTARTMENU,
            Windows = Shell32.ShellSpecialFolderConstants.ssfWINDOWS,
            Printers = Shell32.ShellSpecialFolderConstants.ssfPRINTERS,
            RecycleBin = Shell32.ShellSpecialFolderConstants.ssfBITBUCKET,
            Cookies = Shell32.ShellSpecialFolderConstants.ssfCOOKIES,
            ApplicationData = Shell32.ShellSpecialFolderConstants.ssfAPPDATA,
            SendTo = Shell32.ShellSpecialFolderConstants.ssfSENDTO,
            StartUp = Shell32.ShellSpecialFolderConstants.ssfSTARTUP
        }
        #endregion

        #region FolderTreeView Methods

        #region GetFilePath
        public static string GetFilePath(TreeNode tn)
        {
            try
            {
                var folderItem = (Shell32.FolderItem)tn.Tag;
                string folderPath = folderItem.Path;
                if (Directory.Exists(folderPath))
                    return folderPath;
                return "";
            }
            catch
            {
                return "";
            }
        }
        #endregion

        #region Populate Tree
        public static void PopulateTree(TreeView tree, ImageList imageList)
        {
            int imageCount = imageList.Images.Count - 1;
            AddRootNode(tree, ref imageCount, imageList, ShellFolder.Desktop, true);
            if (tree.Nodes.Count > 1)
            {
                tree.SelectedNode = tree.Nodes[1];
                ExpandBranch(tree.Nodes[1], imageList);
            }
        }
        #endregion

        #region Add Root Node
        private static void AddRootNode(TreeView tree, ref int imageCount, ImageList imageList, ShellFolder shellFolder, bool getIcons)
        {
            Shell32.Shell shell32 = new Shell32.ShellClass();
            Shell32.Folder shell32Folder = shell32.NameSpace(shellFolder);
            Shell32.FolderItems items = shell32Folder.Items();

            if (tree.InvokeRequired)
                tree.Invoke(new Action<bool>(n => tree.Nodes.Clear()), false);
            else
                tree.Nodes.Clear();

            var desktop = new TreeNode("Desktop", 0, 0);
            // Added in version 1.11
            // add a FolderItem object to the root (Desktop) node tag that corresponds to the DesktopDirectory namespace
            // This ensures that the GetSelectedNodePath will return the actual Desktop folder path when queried.
            // There's possibly a better way to create a Shell32.FolderItem instance for this purpose, 
            // but I surely don't know it

            Shell32.Folder dfolder = shell32.NameSpace(ShellFolder.DesktopDirectory);
            foreach (Shell32.FolderItem fi in dfolder.ParentFolder.Items())
            {
                if (fi.Name == dfolder.Title)
                {
                    desktop.Tag = fi;
                    break;
                }
            }
            
            // iterate through the Desktop namespace and populate the first level nodes
            foreach (Shell32.FolderItem item in items)
            {
                if (item.IsFolder) // this ensures that desktop shortcuts etc are not displayed
                {
                    TreeNode tn = AddTreeNode(item, ref imageCount, imageList, getIcons);
                    desktop.Nodes.Add(tn);
                    CheckForSubDirs(tn);
                }
            }

            // Add the Desktop root node to the tree
            if (tree.InvokeRequired)
                tree.Invoke(new Action<TreeNode>(n => tree.Nodes.Add(n)), desktop);
            else
                tree.Nodes.Add(desktop);
        }
        #endregion

        #region Create Dummy Node
        private static void CheckForSubDirs(TreeNode tn)
        {
            if (tn.Nodes.Count == 0)
            {
                try
                {
                    // create dummy nodes for any subfolders that have further subfolders
                    var folderItem = (Shell32.FolderItem)tn.Tag;
                    var folder = (Shell32.Folder)folderItem.GetFolder;

                    bool hasFolders = false;
                    foreach (Shell32.FolderItem item in folder.Items())
                    {
                        if (item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
                        {
                            hasFolders = true;
                            break;
                        }
                    }
                    if (hasFolders)
                    {
                        var ntn = new TreeNode {Tag = "DUMMYNODE"};
                        tn.Nodes.Add(ntn);
                    }
                }
                catch
                { }
            }
        }
        #endregion

        #region Expand Branch
        public static void ExpandBranch(TreeNode tn, ImageList imageList)
        {
            // if there's a dummy node present, clear it and replace with actual contents
            if (tn.Nodes.Count == 1 && tn.Nodes[0].Tag.ToString() == "DUMMYNODE")
            {
                tn.Nodes.Clear();
                var folderItem = (Shell32.FolderItem)tn.Tag;
                var folder = (Shell32.Folder)folderItem.GetFolder;
                int imageCount = imageList.Images.Count - 1;
                foreach (Shell32.FolderItem item in folder.Items())
                {
                    if (item.IsFileSystem && item.IsFolder && !item.IsBrowsable)
                    {
                        TreeNode ntn = AddTreeNode(item, ref imageCount, imageList, true);
                        tn.Nodes.Add(ntn);
                        CheckForSubDirs(ntn);
                    }
                }
            }
        }
        #endregion

        #region Add Tree Node
        private static TreeNode AddTreeNode(Shell32.FolderItem item, ref int imageCount, ImageList imageList, bool getIcons)
        {
            var tn = new TreeNode {Text = item.Name, Tag = item};

            if (getIcons)
            {
                try
                {
                    imageList.Images.Add(ExtractIcons.GetIcon(item.Path, false, imageList)); // normal icon
                    imageList.Images.Add(ExtractIcons.GetIcon(item.Path, true, imageList)); // selected icon
                    imageCount++;
                    tn.ImageIndex = imageCount;
                    imageCount++;
                    tn.SelectedImageIndex = imageCount;
                }
                catch // use default 
                {
                    tn.ImageIndex = 1;
                    tn.SelectedImageIndex = 2;
                }
            }
            else // use default
            {
                tn.ImageIndex = 1;
                tn.SelectedImageIndex = 2;
            }
            return tn;
        }

        #endregion

        #endregion
    }
}
